<?php
namespace Poi\EttsBundle\Model;

/**
 * @author Ricardo J. Vazquez
 * 
 * Abstract class that represents object from the database. Has basic methods for loading/updating/creating/deleting.
 */
abstract class Model
{
	protected $loadStmt = null; //Statement
	protected $updateStmt = null; //Statement
	protected $createStmt = null; //Statement
	protected $deleteStmt = null; //Statement
	protected $conn = null; //Connection
	protected $isLoaded = false;
	protected $id;
	protected $updateBuffer = array();
	
	
	/**
	* Creates the object.
	*
	* @param $conn connection object to the database
	* @param $id optional argument giving an id for the object
	*/
	public function __construct($conn, $id=''){
		$this->conn = $conn;
		$this->id = $id;
		
		$this->init();
	}
	
	/**
	* Abstract. It is called when the object is created.
	*/
	abstract protected function init();
	
	/**
	* Returns an XML string representation of the object.
	*
	* @param $xml optional. reference to a SimpleXMLElement to which a child element will be added
	*/
	abstract public function toXML(\SimpleXMLElement &$xml = null);
	
	/**
	* Returns an xml string with an error message
	*
	* @param $msg optional message to be displayed instead of the default
	* @return string with the xml
	*/
	public function sendErr($msg = ''){
		$xml = new \SimpleXMLElement('<?xml version="1.0" encoding="utf-8" ?><xmlresponse></xmlresponse>');
				
		if(empty($msg)){
			$xml->addChild('error',"Error occured.");
		}
		else{
			$xml->addChild('error',$msg);
		}
				
		return $xml->asXML();
	}
	
	/**
	* Returns an xml string with an acknowledge message
	*
	* @param $msg optional message to be displayed instead of the default
	* @return string with the xml
	*/
	public function sendAck($msg = ''){
		$xml = new \SimpleXMLElement('<?xml version="1.0" encoding="utf-8" ?><xmlresponse></xmlresponse>');
				
		if(empty($msg)){
			$xml->addChild('ack','Everything went according to plan.');
		}
		else{
			$xml->addChild('ack',$msg);
		}
		
		return $xml->asXML();
	}
	
	/**
	* Abstract. This is called by load(). It prepares a statement which loads information to the object.
	*/
	abstract protected function prepareLoadQuery();
	
	/**
	* Executes the prepared statement and stores the result into the according parameters from the database..
	*
	* @return reference to the object
	*/
    public function load() {
		if(is_null($this->loadStmt)){
			$this->prepareLoadQuery();
		}

		$this->loadStmt->execute();
		
		$this->loadStmt->getWrappedStatement()->setFetchMode(\PDO::FETCH_INTO, $this);
		$this->loadStmt->fetch(\PDO::FETCH_INTO);
		
		$this->isLoaded = true;
		
		return $this;			
    }

	/**
	* Sets the status of the object as loaded or not
	*
	* @param $is boolean that gives the state of the object
	*/
	public function setLoaded($is){
		$this->isLoaded = $is;
	}

	/**
	* Abstract. This is called by update(). It prepares a statement which updates information in the object from the database..
	*/
	abstract protected function prepareUpdateQuery();

	/**
	* Executes the statement for updating. If an exception is encountered it will rollback and send an error.
	*
	* @return string with status of the execute.
	*/
	public function update(){
		if(is_null($this->updateStmt)){
			$this->prepareUpdateQuery();
		}
		
		$this->conn->beginTransaction();
		try{
		    // do stuff
			$this->updateStmt->execute();

		    $this->conn->commit();
			
			foreach($this->updateBuffer as $property=>$value){
				$method = "set$property";
				$this->$method($value);
			}
			
			return $this->sendAck('Updated succesfully!');
			
		} catch(\Exception $e) {
		    $this->conn->rollback();
		
			//return error message
		    return $this->sendErr('Something went wrong: ' . $e->getMessage());
		}
	}
	
	/**
	* Abstract. This is called by create(). It prepares a statement which creates the object from the database..
	*/
	abstract protected function prepareCreateQuery();
	
	/**
	* Executes the statement for creating. If an exception is encountered it will rollback and send an error.
	*
	* @return string with status of the execute.
	*/
	public function create(){
		if(is_null($this->createStmt)){
			$this->prepareCreateQuery();
		}		
		
		$this->conn->beginTransaction();
		try{
		    // do stuff
			$this->createStmt->execute();

		    $this->conn->commit();
			
			// $this->isLoaded = true;
			
			//Return acknowledge
			return $this->sendAck('Created succesfully!');
			
			
		} catch(\Exception $e) {
		    $this->conn->rollback();
			//return error message
			return $this->sendErr('Something went wrong: ' . $e->getMessage());	
		}
		
	}
	
	/**
	* Abstract. This is called by delete(). It prepares a statement which deletes the object from the database.
	*/
	abstract protected function prepareDeleteQuery();
	
	/**
	* Executes the statement for deleting. If an exception is encountered it will rollback and send an error.
	*
	* @return string with status of the execute.
	*/
	public function delete() {
		if(is_null($this->deleteStmt)){
			$this->prepareDeleteQuery();
		}

		$this->conn->beginTransaction();
		try{
		    // do stuff
			$this->deleteStmt->execute();

		    $this->conn->commit();
						
			//Return acknowledge
			return $this->sendAck('Deleted succesfully!');
						
		} catch(\Exception $e) {
		    $this->conn->rollback();
			//return error message
			return $this->sendErr('Something went wrong: ' . $e->getMessage());	
		}
	}
	
	/**
	* Magic method. It is used for making dynamic getters/setters.
	*
	* @param $strFunction the name of the called function
	* @param $arArguments arguments that where sent with the function
	* @return boolean with status if it went wrong
	*/
	public function __call($strFunction, $arArguments) {
		$strMethodType = substr($strFunction, 0, 3); 
		$strMethodMember = substr($strFunction, 3); 
		switch ($strMethodType) {
			case 'set': 
				return($this->SetAccessor($strMethodMember,$arArguments[0]));
				break;
			case 'get': 
				return($this->GetAccessor($strMethodMember));
				break;
			case 'upd':
				return($this->UpdateAccesor($strMethodMember,$arArguments[0]));
		}; 

		return(false);
	}

	/**
	* It is used for dynamic setting of parameters
	*
	* @param $strMember the name of the parameter
	* @param $strNewValue value for the parameter
	* @return boolean with status if it went wrong
	*/
	private function SetAccessor($strMember, $strNewValue) {
		if (property_exists($this, $strMember)) {
			$this->$strMember  = $strNewValue;
		}
		else {
			return false;
		}       
	}

	/**
	* It is used for "updating" of parameters. Instead of changing the parameter the new value is added to a buffer for later use
	*
	* @param $strMember the name of the parameter
	* @param $strNewValue value for the parameter
	* @return boolean with status if it went wrong
	*/
	private function UpdateAccesor($strMember, $strNewValue) {
		if (property_exists($this, $strMember)) {
			$this->updateBuffer[$strMember] = $strNewValue;
		}
		else {
			return false;
		}
	}
	
	/**
	* It is used for dynamic getting of parameters
	*
	* @param $strMember the name of the parameter
	* @return the value stored in the parameter.
	*/
	private function GetAccessor($strMember) {
		if (property_exists($this, $strMember)) {
			if(!$this->isLoaded){
				$this->load();
			}
			$strRetVal = $this->$strMember;
			return($strRetVal);
		}
		else {
			return false;
		}
	}
}